﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using CrmCustomInstallActions.CrmSdk;
using System.Xml;

namespace CrmCustomInstallActions.Commands
{
    class InstallPlugin : GenericCrmSolutionCommand
    {
        private Solution solutionInstaller = null;
        private XmlNode solutionNode = null;

        internal override bool InitializeSpecificCommand()
        {
            try
            {
                solutionNode = Config.SelectSingleNode("Solution");
                WriteLog("Initializing the Plugins/Workflows stored in " + solutionNode.Attributes["Assembly"].InnerText);
                solutionInstaller = new Solution(solutionNode, TargetDir);
                return true;
            }
            catch (Exception e)
            {
                Exceptions.Add("Error initializing the Plugin to be installed", e.Message);
                return false;
            }
        }

        public override bool ExecuteCommand(CommandModes CommandMode)
        {
            if (CommandMode == CommandModes.Install)
            {
                WriteLog("Removing existing plugins registered for '" + solutionNode.Attributes["Assembly"].Value);
                try
                {
                    int i = solutionInstaller.DeleteDuplicates(service);
                    if (i > 0)
                    {
                        WriteLog("Removed " + i.ToString() + " existing Plugins/Workflows");
                    }
                }
                catch (Exception e)
                {
                    Exceptions.Add("Error removing existing Plugins/Workflows. These has to be unregistered before new ones can be registered", e.Message);
                    WriteLog("Error removing existing Plugins/Workflows");
                    return false;
                }


                Guid PluginAssemblyId = Guid.Empty;
                int progress = 100 - solutionInstaller.workflowtypes.Length;
                try
                {
                    ReportProgress(progress, "Registering the Custom Plugin/Workflow " + solutionInstaller.pluginassembly.name);
                    RegisterSolutionRequest request = new RegisterSolutionRequest();
                    request.PluginAssembly = solutionInstaller.PluginAssembly;
                    request.Steps = solutionInstaller.Steps;
                    RegisterSolutionResponse response = (RegisterSolutionResponse)service.Execute(request);
                    PluginAssemblyId = response.PluginAssemblyId;
                    WriteLog("Plugin/Workflow Solution Registered: " + solutionInstaller.pluginassembly.name + ". The Uniquie id of the plugin/workflow assembly is: " + PluginAssemblyId.ToString());
                }
                catch (Exception e)
                {
                    Exceptions.Add("Error registering the Plugin/Workflow solution", e.Message);
                    return false;
                }

                try
                {
                    foreach (plugintype plugintype in solutionInstaller.WorkflowTypes)
                    {
                        progress += 1;
                        ReportProgress(progress, "Registering the command " + plugintype.friendlyname);
                        plugintype.pluginassemblyid = new Lookup();
                        plugintype.pluginassemblyid.type = EntityName.pluginassembly.ToString();
                        plugintype.pluginassemblyid.Value = PluginAssemblyId;
                        plugintype.isworkflowactivity = new CrmBoolean();
                        plugintype.isworkflowactivity.Value = true;
                        service.Create(plugintype);
                        WriteLog(plugintype.friendlyname + " Custom Workflow command Created: " + plugintype.typename);
                    }
                }
                catch (Exception e)
                {
                    Exceptions.Add("Error creating a custom workflow command from the assembly " + solutionInstaller.pluginassembly.name, e.Message);
                    return false;
                }

                return true;
            }
            else if (CommandMode == CommandModes.Rollback)
            {
                WriteLog("Removing existing plugins registered for '" + solutionNode.Attributes["Assembly"].Value);
                try
                {
                    int i = solutionInstaller.DeleteDuplicates(service);
                    if (i > 0)
                    {
                        WriteLog("Removed " + i.ToString() + " existing Plugins/Workflows");
                    }
                }
                catch (Exception e)
                {
                    Exceptions.Add("Error removing existing Plugins/Workflows. These has to be unregistered before new ones can be registered", e.Message);
                    WriteLog("Error removing existing Plugins/Workflows");
                    return false;
                }

                return true;
            }
            else if (CommandMode == CommandModes.Uninstall)
            {
                ReportProgress(50, "Removing Custom Plugin/Workflow commands");
                if (Config.SelectSingleNode("UninstallOption").InnerText.ToUpper() == "ASK")
                {
                    string Question = "This action will uninstall the custom Plugin/Workflow commands, making them unavailable on the CRM Server. Do you want to uninstall them?";
                    string Choice1 = "Yes, uninstall all the Plugin/Workflow commands";
                    string Choice2 = "No, leave them on the server";
                    UI.CrmSolutionInstallerAskForm askForm = new UI.CrmSolutionInstallerAskForm();
                    CrmCustomInstallActions.UI.CrmSolutionInstallerAskForm.ChoiceResult result = askForm.ShowDialog(Question, Choice1, Choice2);
                    askForm = null;
                    if (result == UI.CrmSolutionInstallerAskForm.ChoiceResult.Choice2)
                    {
                        WriteLog("The user has decided to keep the Plugins/Workflows on the server");
                        return true;
                    }
                }
                WriteLog("Removing existing plugins registered for '" + solutionNode.Attributes["Assembly"].Value);
                try
                {
                    int i = solutionInstaller.DeleteDuplicates(service);
                    if (i > 0)
                    {
                        WriteLog("Removed " + i.ToString() + " existing Plugins/Workflows");

                    }
                    ReportProgress(100, "Removed " + i.ToString() + " Plugin/Workflow commands");
                }
                catch (Exception e)
                {
                    Exceptions.Add("Error removing existing Plugins/Workflows. These has to be unregistered before new ones can be registered", e.Message);
                    WriteLog("Error removing existing Plugins/Workflows");
                    return false;
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        public override bool SupportsUninstall
        {
            get { return true; }
        }

        public override bool SupportsUserInterface
        {
            get { return true; }
        }

        #region Private solution class based on the Plugin Developer 0.3 from Microsoft (CrmSdk version 4.0.8)
        private class Solution
        {
            public pluginassembly pluginassembly;
            public SdkMessageProcessingStepRegistration[] steps;
            public plugintype[] workflowtypes;

            public Solution(XmlNode solutionnode, string Path)
            {
                pluginassembly = new pluginassembly();
                FileInfo fi = new FileInfo(System.IO.Path.Combine(Path, solutionnode.Attributes["Assembly"].Value));
                string path = fi.FullName;

                Assembly assembly = Assembly.LoadFile(path);
                byte[] bytes = File.ReadAllBytes(path);
                string[] assemblyProp = assembly.GetName().FullName.Split(",= ".ToCharArray(),
                    StringSplitOptions.RemoveEmptyEntries);
                pluginassembly.content = Convert.ToBase64String(bytes);
                pluginassembly.culture = assemblyProp[4];
                pluginassembly.name = assemblyProp[0];
                pluginassembly.publickeytoken = assemblyProp[6];
                pluginassembly.sourcetype = new Picklist();
                pluginassembly.sourcetype.Value = Convert.ToInt32(solutionnode.Attributes["SourceType"].Value);
                pluginassembly.version = assemblyProp[2];

                XmlNodeList workflowtypenodes = solutionnode.SelectNodes("WorkflowTypes/WorkflowType");
                workflowtypes = new plugintype[workflowtypenodes.Count];

                if (workflowtypenodes.Count > 0)
                {
                    for (int a = 0; a < workflowtypenodes.Count; a++)
                    {
                        workflowtypes[a] = new plugintype();
                        workflowtypes[a].typename = workflowtypenodes[a].Attributes["TypeName"].Value;
                        workflowtypes[a].friendlyname = workflowtypenodes[a].Attributes["FriendlyName"].Value;
                    }
                }

                XmlNodeList stepnodes = solutionnode.SelectNodes(".//Steps/Step");
                steps = new SdkMessageProcessingStepRegistration[stepnodes.Count];
                if (stepnodes.Count > 0)
                {
                    for (int b = 0; b < stepnodes.Count; b++)
                    {
                        steps[b] = new SdkMessageProcessingStepRegistration();
                        steps[b].CustomConfiguration = stepnodes[b].Attributes["CustomConfiguration"].Value;
                        steps[b].Description = stepnodes[b].Attributes["Description"].Value;
                        steps[b].InvocationSource = Convert.ToInt32(stepnodes[b].Attributes["InvocationSource"].Value);
                        steps[b].MessageName = stepnodes[b].Attributes["MessageName"].Value;
                        steps[b].Mode = Convert.ToInt32(stepnodes[b].Attributes["Mode"].Value);
                        steps[b].PluginTypeFriendlyName = stepnodes[b].Attributes["PluginTypeFriendlyName"].Value;
                        steps[b].PluginTypeName = stepnodes[b].Attributes["PluginTypeName"].Value;
                        steps[b].Stage = Convert.ToInt32(stepnodes[b].Attributes["Stage"].Value);
                        steps[b].SupportedDeployment = Convert.ToInt32(stepnodes[b].Attributes["SupportedDeployment"].Value);

                        string FilteringAttributes = stepnodes[b].Attributes["FilteringAttributes"].Value;
                        if (FilteringAttributes.Length > 0)
                        {
                            steps[b].FilteringAttributes = stepnodes[b].Attributes["FilteringAttributes"].Value;
                        }

                        string ImpersonatingUserId = stepnodes[b].Attributes["ImpersonatingUserId"].Value;
                        if (ImpersonatingUserId.Length > 0)
                        {
                            steps[b].ImpersonatingUserId = new Guid(stepnodes[b].Attributes["ImpersonatingUserId"].Value);
                        }

                        string PrimaryEntityName = stepnodes[b].Attributes["PrimaryEntityName"].Value;
                        if (PrimaryEntityName.Length > 0)
                        {
                            steps[b].PrimaryEntityName = stepnodes[b].Attributes["PrimaryEntityName"].Value;
                        }

                        string SecondaryEntityName = stepnodes[b].Attributes["SecondaryEntityName"].Value;
                        if (SecondaryEntityName.Length > 0)
                        {
                            steps[b].SecondaryEntityName = stepnodes[b].Attributes["SecondaryEntityName"].Value;
                        }

                        XmlNodeList imagenodes = stepnodes[b].SelectNodes("Images/Image");
                        if (imagenodes.Count > 0)
                        {
                            steps[b].Images = new SdkMessageProcessingStepImageRegistration[imagenodes.Count];
                            for (int c = 0; c < imagenodes.Count; c++)
                            {
                                steps[b].Images[c] = new SdkMessageProcessingStepImageRegistration();
                                steps[b].Images[c].EntityAlias = imagenodes[c].Attributes["EntityAlias"].Value;
                                steps[b].Images[c].ImageType = Convert.ToInt32(imagenodes[c].Attributes["ImageType"].Value);
                                steps[b].Images[c].MessagePropertyName = imagenodes[c].Attributes["MessagePropertyName"].Value;

                                string AttributeArray = imagenodes[c].Attributes["Attributes"].Value;
                                if (AttributeArray != null)
                                {
                                    steps[b].Images[c].Attributes = AttributeArray.Split(',');
                                }

                            }
                        }
                    }
                }
            }

            public pluginassembly PluginAssembly
            {
                get
                {
                    return pluginassembly;
                }
            }

            public SdkMessageProcessingStepRegistration[] Steps
            {
                get
                {
                    return steps;
                }
            }

            public plugintype[] WorkflowTypes
            {
                get
                {
                    return workflowtypes;
                }
            }

            public int DeleteDuplicates(CrmService service)
            {
                ColumnSet cols = new ColumnSet();
                cols.Attributes = new string[] { "pluginassemblyid", "name" };
                ConditionExpression cond = new ConditionExpression();
                cond.AttributeName = "name";
                cond.Operator = ConditionOperator.Equal;
                cond.Values = new object[] { pluginassembly.name };
                FilterExpression filter = new FilterExpression();
                filter.FilterOperator = LogicalOperator.And;
                filter.Conditions = new ConditionExpression[] { cond };
                QueryExpression query = new QueryExpression();
                query.ColumnSet = cols;
                query.Criteria = filter;
                query.EntityName = EntityName.pluginassembly.ToString();
                RetrieveMultipleRequest request = new RetrieveMultipleRequest();
                request.Query = query;
                RetrieveMultipleResponse response = (RetrieveMultipleResponse)service.Execute(request);
                int dupes = response.BusinessEntityCollection.BusinessEntities.Length;
                if (dupes > 0)
                {
                    for (int i = 0; i < dupes; i++)
                    {
                        pluginassembly pa = (pluginassembly)response.BusinessEntityCollection.BusinessEntities[i];
                        Console.WriteLine(pa.pluginassemblyid.Value.ToString());
                        UnregisterSolutionRequest unregister = new UnregisterSolutionRequest();
                        unregister.PluginAssemblyId = pa.pluginassemblyid.Value;
                        service.Execute(unregister);
                    }
                }
                return dupes;
            }
        }
        #endregion



    }
}
